#include "GameEngine/3DModel/Model3D.h"
#define TESTNM
bool Model3D::isInited=false;
PVRTMATRIX Model3D::cameraCorrection=PVRTMATRIX();
void Model3D::init(){
	if(isInited)
		return;

	PVRTMATRIXf	rotx,roty,flip;
	PVRTMatrixIdentity(cameraCorrection);
	PVRTMatrixRotationX(rotx,3.14159/2);
	PVRTMatrixRotationY(roty,3.14159);
	PVRTMatrixScaling(flip,1.0f,1.0f,1.0f);
	PVRTMatrixMultiply(cameraCorrection, cameraCorrection, flip);
	PVRTMatrixMultiply(cameraCorrection, cameraCorrection, rotx);
	PVRTMatrixMultiply(cameraCorrection, cameraCorrection, roty);

	isInited=true;
}

Model3D::Model3D(const char *c_szSceneFile): m_puiVbo(0), m_puiIndexVbo(0), m_fFrame(0), m_TexID(0){
	init();
	struct stat fi;
	if(stat(c_szSceneFile,&fi)!=0)
		return; //file does not exist
	if(model.ReadFromFile(c_szSceneFile) != PVR_SUCCESS)
	{
		printf("Error: Failed to load scene.\n");
		return;
	}
	genVBO();
	loadTextures();
	m_fAngle=0;
}

void Model3D::DrawModel(PVRTMATRIXf m_mView){
	
	::glFrontFace(GL_CW);
	::glEnable(GL_CULL_FACE);
	::glCullFace(GL_BACK);
	::glEnable(GL_DEPTH_TEST);
	
	::glColor4f(1.0f,1.0f,1.0f,1.0f);

	model.SetFrame(m_fFrame);

	m_fFrame += f2vt(1.0f);
	while(m_fFrame > f2vt(model.nNumFrame-1))
		m_fFrame -= f2vt(model.nNumFrame-1);
	

	int trianglecount=0;

	//::glRotatef(90,1,0,0);
	//::glRotatef(180,0,1,0);
	//::glScalef(1.0f,1.0f,-1.0f);
	
	for(unsigned int i=0; i<model.nNumMeshNode; i++){

		SPODNode *pNode = &model.pNode[i];
		SPODMesh *pMesh = &model.pMesh[pNode->nIdx];

		trianglecount+=pMesh->nNumFaces;

		//loading data
		::glBindBuffer(GL_ARRAY_BUFFER, m_puiVbo[i]);
		::glVertexPointer(pMesh->sVertex.n, VERTTYPEENUM, pMesh->sVertex.nStride,pMesh->sVertex.pData);
		::glEnableClientState(GL_VERTEX_ARRAY);

		//if the data is interleaved don't disable the buffer
		if(!isInterleaved)
			::glBindBuffer(GL_ARRAY_BUFFER, 0);

		bool bSkinning = pMesh->sBoneWeight.pData != 0;
		if(bSkinning){
			::glEnable(GL_MATRIX_PALETTE_OES);
			::glMatrixMode(GL_MATRIX_PALETTE_OES);

			PVRTMat4	mBoneWorld;
			int			i32NodeID;

			for(int j = 0; j < pMesh->sBoneBatches.pnBatchBoneCnt[0]; ++j){

				m_Extensions.glCurrentPaletteMatrixOES(j);

				i32NodeID = pMesh->sBoneBatches.pnBatches[j];
				model.GetBoneWorldMatrix(mBoneWorld, *pNode, model.pNode[i32NodeID]);
				

				//we need to multyply the bone matrix with the model view matrix from current camera because the model view matrix is ignored
				PVRTMatrixMultiply(mBoneWorld, mBoneWorld, Model3D::cameraCorrection);
				PVRTMatrixMultiply(mBoneWorld, mBoneWorld, m_mView);
				
				::glLoadMatrixf(mBoneWorld.f);
			}

			::glEnableClientState(GL_MATRIX_INDEX_ARRAY_OES);
			::glEnableClientState(GL_WEIGHT_ARRAY_OES);

			m_Extensions.glMatrixIndexPointerOES(pMesh->sBoneIdx.n, GL_UNSIGNED_BYTE, pMesh->sBoneIdx.nStride, pMesh->sBoneIdx.pData);
			m_Extensions.glWeightPointerOES(pMesh->sBoneWeight.n, GL_FLOAT, pMesh->sBoneWeight.nStride, pMesh->sBoneWeight.pData);

		} else {
			::glDisable(GL_MATRIX_PALETTE_OES);
			//if the matrix pallete is active the modelview matrix is ignored
			::glMatrixMode(GL_MODELVIEW);
			::glPushMatrix();
			PVRTMat4 worldMatrix;
			model.GetWorldMatrix(worldMatrix, *pNode);
			::glMultMatrixf(worldMatrix.f);
			::glMultMatrixf(Model3D::cameraCorrection.f);
			//::glRotatef(90.0f,1.0f,0.0f,0.0f);
			//::glRotatef(180.0f,0.0f,1.0f,0.0f);
			//::glScalef(1.0f,1.0f,-1.0f);
		}
		
//TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST 
#ifndef TESTNM
		::glClientActiveTexture(GL_TEXTURE0);
		::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		::glTexCoordPointer(pMesh->psUVW[0].n, VERTTYPEENUM, pMesh->psUVW[0].nStride, pMesh->psUVW[0].pData);

		::glActiveTexture(GL_TEXTURE0);
		::glEnable(GL_TEXTURE_2D);
		
		::glBindTexture(GL_TEXTURE_2D,m_TexID[0]);
		::glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
#else
		
		::glClientActiveTexture(GL_TEXTURE0);
		::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		::glTexCoordPointer(pMesh->psUVW[0].n, VERTTYPEENUM, pMesh->psUVW[0].nStride, pMesh->psUVW[0].pData);
		
		::glClientActiveTexture(GL_TEXTURE1);
		::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		::glTexCoordPointer(pMesh->psUVW[0].n, VERTTYPEENUM, pMesh->psUVW[0].nStride, pMesh->psUVW[0].pData);
		
		::glActiveTexture(GL_TEXTURE0);
		::glEnable(GL_TEXTURE_2D);
		
		::glActiveTexture(GL_TEXTURE1);
		::glEnable(GL_TEXTURE_2D);

//LV
		Vec3f fLightVector;
		
#define PIf			(3.1415926535f)
		// move the light vector around the cube
		//++m_fAngle;
		m_fAngle=-45;
		//if(m_fAngle > f2vt(90.0f))
		//	m_fAngle = f2vt(0.0f);
		
		
		// Set up the light vector and rotate it round the cube.
		
		fLightVector.x = sin(f2vt(m_fAngle * (PIf / 180.0f)));
		fLightVector.y = f2vt(0.0f);
		fLightVector.z = cos(f2vt(m_fAngle * (PIf / 180.0f)));
		
		// Half shifting to have a value between 0.0f and 1.0f
		fLightVector.x = VERTTYPEMUL(fLightVector.x, f2vt(0.5f)) + f2vt(0.5f);
		fLightVector.y = VERTTYPEMUL(fLightVector.y, f2vt(0.5f)) + f2vt(0.5f);
		fLightVector.z = VERTTYPEMUL(fLightVector.z, f2vt(0.5f)) + f2vt(0.5f);
		
		// Set light direction as a colour
		// (the colour ordering depend on how the normal map has been computed)
		// red=y, green=z, blue=x
		glColor4f(fLightVector.y, fLightVector.z, fLightVector.x, 0);
//LV

		::glActiveTexture(GL_TEXTURE0);
		::glBindTexture(GL_TEXTURE_2D,m_TexID[1]);
		
		::glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
		::glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGBA);
		::glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
		::glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
		
		::glActiveTexture(GL_TEXTURE1);
		::glBindTexture(GL_TEXTURE_2D, m_TexID[0]);
		
		// Set the texture environment mode for this texture to combine
		::glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
		
		// Set the method we're going to combine the two textures by.
		::glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
		
		// Use the previous combine texture as source 0
		::glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
		
		// Use the current texture as source 1
		::glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE);
		
		
		//Set what we will operate on, in this case we are going to use
		//just the texture colours.
		
		::glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		::glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
		
#endif
//TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST 

		//draw everything
		if(isIndexedFaces){ //not recomended
			::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_puiIndexVbo[i]);
			::glDrawElements(GL_TRIANGLES, pMesh->nNumFaces*3, GL_UNSIGNED_SHORT, pMesh->sFaces.pData);
			::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
		} else {
			::glDrawArrays(GL_TRIANGLES, 0, pMesh->nNumVertex);
		}

		::glBindBuffer(GL_ARRAY_BUFFER, 0);
#ifndef TESTNM
		::glDisableClientState(GL_VERTEX_ARRAY);
#else
		/*Disable the buffer for the second set of texture coordinates*/
		::glClientActiveTexture(GL_TEXTURE1);
		::glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		
		/* Disable 2D texturing for the second texture */
		::glActiveTexture(GL_TEXTURE1);
		::glDisable(GL_TEXTURE_2D);

		/*Disable the buffer for the first set of texture coordinates*/
		::glClientActiveTexture(GL_TEXTURE0);
		::glDisableClientState(GL_TEXTURE_COORD_ARRAY);

		/* Disable 2D texturing for the first texture */
		::glActiveTexture(GL_TEXTURE0);
		::glDisable(GL_TEXTURE_2D);

		::glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

#endif
		if(bSkinning) {
			::glDisableClientState(GL_MATRIX_INDEX_ARRAY_OES);
			::glDisableClientState(GL_WEIGHT_ARRAY_OES);
			::glDisable(GL_MATRIX_PALETTE_OES);
		} else {
			// restore the modelview matrix
			::glPopMatrix();
		}

	}
	//printf("traingles: %d\n",trianglecount);
}

bool Model3D::genVBO(){

	unsigned char *bufferPointer=0;
	isInterleaved=false;

	m_puiVbo=new GLuint[model.nNumMesh];

	::glGenBuffers(model.nNumMesh, m_puiVbo);

	if(model.pMesh[0].sFaces.pData){ //indexed faces
		m_puiIndexVbo=new GLuint[model.nNumMesh];
		::glGenBuffers(model.nNumMesh, m_puiIndexVbo);
		isIndexedFaces=true;
	} else {
		isIndexedFaces=false;
	}

	if(model.pMesh[0].pInterleaved != 0)
		isInterleaved=true;
	else
		isInterleaved=false;


	for(unsigned int i=0; i<model.nNumMesh; i++){

		SPODMesh& Mesh = model.pMesh[i];

		if(isInterleaved) //interleaved is the better choice
			bufferPointer=Mesh.pInterleaved;
		else
			bufferPointer=Mesh.sVertex.pData;

		//load the vertex buffer
		::glBindBuffer(GL_ARRAY_BUFFER, m_puiVbo[i]);
		::glBufferData(GL_ARRAY_BUFFER, Mesh.nNumVertex * Mesh.sVertex.nStride, bufferPointer, GL_STATIC_DRAW);

		//if the data is not interleaved we can free some memory otherwise the pData pointer is the offset in the array
		if(!isInterleaved){
			delete (Mesh.sVertex.pData);
			Mesh.sVertex.pData=0;
		} else {
			delete (Mesh.pInterleaved);
			Mesh.pInterleaved=0;
		}

		//load the index buffer if exist
		if(isIndexedFaces){ //not recomended
			::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_puiIndexVbo[i]);
			::glBufferData(GL_ELEMENT_ARRAY_BUFFER, PVRTModelPODCountIndices(Mesh) * sizeof(GLshort), Mesh.sFaces.pData, GL_STATIC_DRAW);

			//free more memory
			delete (Mesh.sFaces.pData);
			Mesh.sFaces.pData=0;
		}
	}

	::glBindBuffer(GL_ARRAY_BUFFER, 0);
	::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	return true;
}

bool Model3D::loadTextures(){
	/*
	m_TexID =new GLuint[model.nNumTexture];
	for(unsigned int i=0;i<model.nNumTexture;i++){
		m_TexID[i]=TextureManager::loadTextureFromFile(model.pTexture[i].pszName);
		if(m_TexID[i] == INVALID_TEXTURE){
			return false;
		}
		::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	*/
	m_TexID =new GLuint[2];
	
	m_TexID[0]=TextureManager::loadTextureFromFile("RES/modelo.pvr");
	if(m_TexID[0] == INVALID_TEXTURE){
		return false;
	}
	::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#ifdef TESTNM
	m_TexID[1]=TextureManager::loadTextureFromFile("RES/modelonm.pvr");
	if(m_TexID[1] == INVALID_TEXTURE){
		return false;
	}
	::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#endif
	return true;
}